﻿using System;
using System.Collections.Generic;
using System.Text;

namespace Ndo.Maths
{
    public delegate IList<double> CrowdEvaluator<Vector>(IList<Vector> a) where Vector : IList<double>;
    public delegate IList<double> PartialCrowdEvaluator<Vector>(IList<Vector> a, int firstN) where Vector : IList<double>;
    public class CrowdEvaluation<Vector>
        where Vector:IList<double>
    {
        public readonly DistanceHandler<IList<double>> Distance;
        public CrowdEvaluation(DistanceHandler<IList<double>> distance)
        {
            Distance = distance;
        }

        public IList<double> CrowedDistance(IList<Vector> a, int firstN)
        {
            return CrowedDistance(a);
        }
        public IList<double> CrowedDistance(IList<Vector> a)
        {
            if (a.Count == 0)
                return new double[0];
            int n = a.Count, m = a[0].Count;
            double[][] ds = new double[n][];
            for(int i=0; i<n; i++)
            {
                ds[i] = new double[m];
            }
            int[] map = new int[n];
            double[] values = new double[n];
            for(int i=0; i<m; i++)
            {
                for(int j=0; j<n; j++)
                {
                    map[j] = j;
                    values[j] = a[j][i];
                }
                Array.Sort(values, map);
                ds[map[0]][i] = double.PositiveInfinity;
                ds[map[n - 1]][i] = double.PositiveInfinity;
                for(int j=n-2; j>0; j--)
                {
                    ds[map[j]][i] = values[j + 1] - values[j - 1];
                }
            }
            double[] zeros = new double[m];
            for(int i=0; i<n; i++)
            {
                values[i] = Distance(zeros, ds[i]);
            }
            return values;
        }
        //public IList<double> CrowedDistance(IList<Vector> a)
        //{
        //    double[] ret = new double[a.Count];
        //    int n = a.Count;
        //    for (int k = 0; k < n; k++)
        //    {
        //        Vector element = a[k];
        //        Vector min = (Vector)element.Clone(), max = (Vector)element.Clone();
        //        for (int i = 0; i < element.Count; i++)
        //        {
        //            double t = element[i], t1 = double.NegativeInfinity, t2 = double.PositiveInfinity;
        //            foreach (Vector e in a)
        //            {
        //                if (object.ReferenceEquals(e, element))
        //                {
        //                    double d = e[i];
        //                    if (t >= d && d > t1)
        //                    {
        //                        t1 = d;
        //                    }
        //                    if (t <= d && d < t2)
        //                    {
        //                        t2 = d;
        //                    }
        //                }
        //            }
        //            min[i] = t1;
        //            max[i] = t2;
        //        }
        //        ret[k] = Distance(min, max);
        //    }
        //    return ret;
        //}
        private IEnumerable<double[]> distances(IList<Vector> a)
        {
            int n = a.Count;
            for (int i = 0; i < n; i++)
            {
                double[] ds = new double[n];
                Vector e = a[i];
                for (int j = 0; j < n; j++)
                    ds[j] = Distance(e, a[j]);
                yield return ds;
            }
        }
        public IList<double> KthNeighbor(IList<Vector> a)
        {
            return KthNeighbor(a, a.Count);
        }
        public IList<double> KthNeighbor(IList<Vector> a, int firstN)
        {
            int n = a.Count, i=0;
            if(firstN > n)
                firstN = n;
            int K = (int) Math.Ceiling(Math.Sqrt(n)) - 1;
            double[] ret = new double[firstN];
            foreach (double[] ds in distances(a))
            {
                Array.Sort(ds);//TODO: kth optimaze
                ret[i++] = ds[K];
                if(i>=firstN)
                    break;
            }
            return ret;
        }
        public IList<double> GradientK2(IList<Vector> a)
        {
            return GradientK2(a, a.Count);
        }
        public IList<double> GradientK2(IList<Vector> a, int firstN)
        {
            int n = a.Count, i = 0;
            if (firstN > n)
                firstN = n;
            double[] ret = new double[n];
            foreach (double[] ds in distances(a))
            {
                Array.Sort(ds);
                double d = 0;
                for (int j = 1; j < n; j++)
                    d += ds[j] / (j * j);
                ret[i++] = d;
                if (i >= firstN)
                    break;
            }
            return ret;
        }
        public IList<double> Gradient2K(IList<Vector> a)
        {
            return Gradient2K(a, a.Count);
        }
        public IList<double> Gradient2K(IList<Vector> a, int firstN)
        {
            int n = a.Count, i = 0;
            if (firstN > n)
                firstN = n;
            double[] ret = new double[n];
            foreach (double[] ds in distances(a))
            {
                Array.Sort(ds);
                double d = 0;
                double x = 1;
                for (int j = 1; j < n; j++)
                {
                    x *= 2;
                    d += ds[j] / x;
                }
                ret[i++] = d;
                if (i >= firstN)
                    break;
            }
            return ret;
        }
        public IList<double> GradientE(IList<Vector> a)
        {
            return GradientE(a, a.Count);
        }
        public IList<double> GradientE(IList<Vector> a, int firstN)
        {
            int n = a.Count, i = 0;
            if (firstN > n)
                firstN = n;
            double[] ret = new double[n];
            foreach (double[] ds in distances(a))
            {
                Array.Sort(ds);
                double d = 0;
                double x = 1;
                for (int j = 1; j < n; j++)
                {
                    x *= j;
                    d += ds[j]/x;
                }
                ret[i++] = d;
                if (i >= firstN)
                    break;
            }
            return ret;
        }

    }
}
